Unity3D mecanim杂记

以下皆以4.5左右版本Unity3D为准

1:Layer

Mecanim的layer可以在一个角色上播放超过一个动画。
较高的layer会盖过下面的layer, 不过layer 0以外的layer weight预设是0, 要set为1才会播放。
例如layer 0是站立或跑走的动画 layer 1是 挥剑的动画
用body mask控制layer 1只影响上半身
脚本中要挥剑就 Animator.SetLayerWeight(1,1f);
完成后Animator.SetLayerWeight(1,0f);

base layer以上的新layer初始的weight值是0, 一定要用script將weight改成1才可以播动画
当base layer和new layer同時播放影响同一关节的动画时,会只播放层数高的layer

这些后来增加的层的权重在运行的时候默认是0的,也就是没有影响,需要自己在初始化的时候或者特定需要的时候设定层的权重。(使用animator.SetLayerWeight)

http://docs.unity3d.com/Documentation/Components/class-AvatarBodyMask.html/
http://docs.unity3d.com/Documentation/Manual/AnimationLayers.html/

2:AnimatorStateInfo

AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
if (stateInfo.IsName("Base Layer.RifleIdle"))
{
    //Debug.Log("stateInfo.length:" + stateInfo.length);
    //Debug.Log("stateInfo.normalizedTime:" + stateInfo.normalizedTime);
    //if (stateInfo.normalizedTime >= stateInfo.length)
    if (stateInfo.normalizedTime >= 0.01f)
    {
        gun.transform.parent = leftHand.transform;//先设置父子关系,再设置位置和旋转。否则会出现闪烁。
        gun.transform.localPosition = leftHandLocalPos;
        gun.transform.localRotation = Quaternion.Euler(leftHandLocalRotation);
    }
}

Animator.GetCurrentAnimatorStateInfo(int layerIndex)//注意该函数最好在update中调用,返回的才是当前实时的animatorStateInfo。若放在Start函数中调用的话,获得的只是启动时候的状态~

AnimatorStateInfo.normalizedTime
Normalized time of the State.
The integer part is the number of time a state has been looped. The fractional part is the % (0-1) of progress in the current loop
normalized time

AnimatorStateInfo.length
Current duration of the state.
In seconds Can vary when the State contains a Blend Tree.

3:Animation Clip Inspector

http://docs.unity3d.com/Documentation/Manual/RootMotion.html/
http://docs.unity3d.com/Documentation/Components/class-AnimationClip.html/
Animation clip inspector
Bake Into pose勾选后,人物就不能跟随动画进行位置的改变相应。
不勾选,人物可以跟随动画进行相应位置的改变。

4:Overriding Root Motion

MonoBehaviour.OnAnimatorMove()
Callback for processing animation movements for modifying root motion.
This callback will be invoked at each frame after the state machines and the animations have been evaluated, but before OnAnimatorIK.

public class Example : MonoBehaviour {
    void OnAnimatorMove() {
          Animator animator = GetComponent<Animator>();
        if (animator) {
            Vector3 newPosition = transform.position;
            newPosition.z += animator.GetFloat("Runspeed") * Time.deltaTime;
            transform.position = newPosition;
        }    
    }
}

一个常规的通过characterController控制移动的写法:

void OnAnimatorMove()
{
    Vector3 deltaPosition = animator.deltaPosition;
    if (controller.isGrounded)
    {
        xVelocity = animator.GetFloat("Speed") * controller.velocity.x * 0.25f;
        zVelocity = animator.GetFloat("Speed") * controller.velocity.z * 0.25f;
    }
    verticalSpeed += Physics.gravity.y * Time.deltaTime;
    if (verticalSpeed <= 0)
    {
        animator.SetBool("Jump", false);
    }

    deltaPosition.y = verticalSpeed * Time.deltaTime;
    if (!controller.isGrounded)
    {
        deltaPosition.x = xVelocity * Time.deltaTime;
        deltaPosition.z = zVelocity * Time.deltaTime;
    }
    controller.Move(deltaPosition);

    if ((controller.collisionFlags & CollisionFlags.Below) != 0)
    {
        verticalSpeed = 0;
    }

    transform.rotation = animator.rootRotation;
}

5 :OnAnimatorIK(int layerIndex)

常用方法:
animator.SetIKPosition(AvatarIKGoal.LeftHand,…);
animator.SetIKPositionWeight();

animator.SetIKRotation
animator.SetIKRotationWeight

animator.SetLookAtPosition
animator.SetLookAtWeight  

6:一些技巧:

6.1:控制blendTree中motion的动画速度

blend tree1
FireStatus参数控制,是蹲下射击还是站立射击。

blend tree2
如上图,通过修改绿色圈的速度可以修改该动画的播放速度,从而提高射击速度。但是目前版本4.3还没有提供直接的api用代码控制它。虽然可以通过设置animator.Speed来整体提高速度,但是会影响到其它动画,可控性很低。
可以通过每个动画单独设置一个blendTree,该blendTree使用两个motion,且这两个motion都添加这个动画,然后设置最低速度、最大速度。然后用一个float型变量控制在这两个motion之间切换。如下图:

blend tree3

blend tree4
这样在程序里结合使用FireSpeed参数来控制射击速度了。

6.2:动画覆盖时候的参数设置解决覆盖扭曲的问题,如站立射击,跑动射击

这里以站立射击,跑动射击为例说明。涉及到的动作有:站立idle状态,站立射击状态,跑动瞄准状态,跑动射击状态。如下图(跑动射击和跑动瞄准不容易区分这里不示意了)。

模型动作示例图
常规做法是站立、跑动放一个layer,射击动作新建一个layer,通过mask,使得下半身不动。调整weight为1,使得射击动作的层覆盖weight较低的层(base layer的weight为0),这里注意一点,weight不一定设置为1,weight值越高则可以覆盖低weight的动作,而且weight值还形象自身动作的力度,weight=1,则按照最大动作幅度表现动作,weight=0.5则动作幅度只有原来的一半。
需要注意射击层的Blending属性。

mask blending type
Additive是在原来动作的基础上只添加新的动作。
Override是覆盖原来动作。
如从站立过渡到站立射击状态,若这里选择的是Additive。相对于站立动作,射击动作就一个手部的抖动,因此现在的动作就是站立手部抖动,看起来蛋疼且莫名其妙。若选择的是Override,一切完好。若非要用Additive不可,那么还需要一个站立瞄准的动作,切换到射击状态前,确保人物已经切换到站立瞄准的状态了,这样在站立瞄准的基础上Additive的射击就不会蛋疼了。
跑动射击的话,我们在跑动瞄准的基础上Additive射击的动作自然没有问题,问题是若使用Override方式的话,很蛋疼。上半身枪口的指向不再是正前方。。。个人猜测是跑动瞄准的脊柱和射击动作的脊柱不一致吧。。。

6.3:RootTransform Rotation BasedUpon参数设置

BasedUpon参数
Original的提示是:Keep the rotation as it is authored in the source file
Body Oritation的提示是:Keeps the upper body pointing forward

6.4 不要把Unity3D工程A中的中的fbx文件拷贝供其它工程使用

这样做在另一个工程中会出现很多意想不到的现象,别的工程要用的话,必须使用源fbx文件。

6.5 Mask,Curves, Events

具体哪个版本的Unity3D为导入的动作加入了这几个东西,已经记不得了。

6.5.1 Mask

6.5.2 Curves

Curves和Events都是一个动作上可以添加多个的。
Curves通过添加曲线来控制动画各个部位的播放速度。
一个动作添加了多个Curve的话,会取均值??
如下图添加了一个叫做RollCurve的Curve,若动作的Animator Controller也声明了float型的“RollCurve”动作,则这个两个便挂钩了。在程序中可以获得当前Curve的值
add curves
Debug.Log(“rollCurveValue is:”+animator.GetFloat(“RollCurve”));
详见:http://docs.unity3d.com/Documentation/Manual/AnimatorCurves.html/

6.5.3 Events

可以为动作添加事件,当动作运行到添加事件的时间点后,会触发事件的执行,同样一个动作上可以添加多个事件,满足条件的时候都会执行的。
add many events
下图为添加事件的窗口。Function后的textbox对应动作控制脚本上的函数名称,该函数对public还是private没有要求,但是该函数只能带0个或者1个参数,参数类型如下图。
Add events

6.6 Any State->Death需要注意的事项

默认从Any State到其它状态添加了动作转换过渡后,默认条件如下图:
any state 2 death
但是该条件一运行便会执行。于是我们想通过某个bool变量如isDeath来控制,于是删除Exit Time这个条件,只使用bool变量来控制。结果是动作会来回一直闪烁,非常奇怪。解决方案是保留该Exit Time条件,根据需要 调节后面的时间,然后结合bool变量来控制

6.7 移除动作模型保留动作文件,无法减少占用空间

由于要在unity3d中调相关的参数,一般从3dsmax中导出的fbx都是带有模型的动作。在unity3d中做好一切工作后,想着可以ctrl+d把动作文件从模型中复制出来(选中动作文件,ctrl+d会自动复制一个到project目录中),然后删除原来的模型,达到减少安装包大小的目地。事实上测试了,无法。Unity其实已经做了相关的优化的,打包的时候打入的就是动作文件, 而不是包括冗余的模型。
需要注意的是,再修改相关参数的话就没法了…需要重新导入模型文件…
另一方面,6.5中提到的设置的events事件在模型文件被删除后依旧会保留的,只是我们看不到了,无法编辑了而已!~~

6.8 SetBool和SetTrigger函数比较,以及bool变量和trigger变量比较

Unity4.3在animator面板内引入了Trigger这个变量(以前只有int ,float,bool三种)

6.8.1 SetBool,SetTrigger函数

在animator界面要是需要用一个bool变量(这里以bool IsAttack为例)来控制动画的来回切换就要先设置IsAttack为true,然后在适当的时候设置为false。
如何获取适当的时候呢?一般都是通过animator.GetCurrentAnimatorStateInfo来获取当前的动画播放信息。
SetBool比较简单,代码如下:

stateInfo0 = animator.GetCurrentAnimatorStateInfo(0);
if(stateInfo0.IsName("Base Layer.Attack"))
{
    if(stateInfo0.normalizedTime>0.9f)
    {
        animator.SetBool("IsAttack",false);
    }
}
if(Input.GetKeyDown(KeyCode.A))
{
    animator.SetBool("IsAttack",true);
}

SetTrigger代码如下(这里用SetTrigger来操纵animator界面中声明为bool类型的变量,发现竟然可以操纵):

stateInfo0 = animator.GetCurrentAnimatorStateInfo(0);
if(stateInfo0.IsName("Base Layer.Attack"))
{
    if(stateInfo0.normalizedTime>0.9f)
    {
        animator.SetBool("IsAttack",false);
    }
}
if(Input.GetKeyDown(KeyCode.A))
{
    animator.SetTrigger("IsAttack");
}

SetTrigger实质其实就是设置了IsAttack为true了。但事实上SetTrigger函数是用来操纵Trigger类型变量的。如下介绍

6.8.2 Bool变量,Trigger变量

http://answers.unity3d.com/questions/600268/mecanim-animation-parameter-types-boolean-vs-trigg.html/
这里有讨论。
下图为使用Trigger变量作为条件的截图,它不像Bool变量那样有true,false之分。
trigger inspector
通过使用SetTrigger函数,可以先设置Tigger变量的值为true,使得进入另一个状态,然后立马Tigger变量又变为false。这样就不用像SetBool函数那边,还要考虑在合适的时候设置为false的问题。
比如人物从Idle状态到攻击状态,通过Tigger类型变量isAttack来控制。
从Idle到攻击,用isAttack来控制。这样我们进入攻击后,该变量又回到false。
攻击再回到Idle,用ExistTime来控制。这样就比较好的解决了问题。